home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / DB / NestedSet.php < prev    next >
PHP Script  |  2004-03-24  |  62KB  |  2,147 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PEAR :: DB_NestedSet                                                 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Daniel Khan <dk@webcluster.at>                              |
  17. // |           Jason Rust  <jason@rustyparts.com>                          |
  18. // +----------------------------------------------------------------------+
  19. // $Id: NestedSet.php,v 1.31 2003/08/13 20:09:29 datenpunk Exp $
  20. //
  21.  
  22. // CREDITS:
  23. // --------
  24. // - Many thanks to Jason Rust for doing great improvements and cleanup work for the current release
  25. // - Thanks to Kristian Koehntopp for publishing an explanation of the Nested Set
  26. //   technique and for the great work he did and does for the php community
  27. // - Thanks to Daniel T. Gorski for his great tutorial on www.develnet.org
  28. // - Thanks to Hans Lellelid for suggesting support for MDB and for helping me with the
  29. //   implementation
  30. //   ...
  31. // - Thanks to my parents for ... just kidding :]
  32.  
  33. require_once 'PEAR.php';
  34.  
  35. // {{{ constants
  36.  
  37. // Error and message codes
  38. define('NESE_ERROR_RECURSION',    'E100');
  39. define('NESE_DRIVER_NOT_FOUND',   'E200');
  40. define('NESE_ERROR_NOHANDLER',    'E300');
  41. define('NESE_ERROR_TBLOCKED',     'E010');
  42. define('NESE_MESSAGE_UNKNOWN',    'E0');
  43. define('NESE_ERROR_NOTSUPPORTED', 'E1');
  44. define('NESE_ERROR_PARAM_MISSING','E400');
  45. define('NESE_ERROR_NOT_FOUND',    'E500');
  46.  
  47. // for moving a node before another
  48. define('NESE_MOVE_BEFORE', 'BE');
  49. // for moving a node after another
  50. define('NESE_MOVE_AFTER', 'AF');
  51. // for moving a node below another
  52. define('NESE_MOVE_BELOW', 'SUB');
  53.  
  54. // }}}
  55. // {{{ DB_NestedSet:: class
  56.  
  57. /**
  58. * DB_NestedSet is a class for handling nested sets
  59. *
  60. * @author       Daniel Khan <dk@webcluster.at>
  61. * @package      DB_NestedSet
  62. * @version      $Revision: 1.31 $
  63. * @access       public
  64. */
  65.  
  66. // }}}
  67. class DB_NestedSet extends PEAR {
  68.     // {{{ properties
  69.     
  70.     /**
  71.     * @var array The field parameters of the table with the nested set. Format: 'realFieldName' => 'fieldId'
  72.     * @access public
  73.     */
  74.     var $params = array(
  75.     'STRID' => 'id',
  76.     'ROOTID'=> 'rootid',
  77.     'l'     => 'l',
  78.     'r'     => 'r',
  79.     'STREH' => 'norder',
  80.     'LEVEL' => 'level',
  81.     'STRNA' => 'name'
  82.     );
  83.     
  84.     /**
  85.     * @var array The above parameters flipped for easy access
  86.     * @access private
  87.     */
  88.     var $flparams = array();
  89.     
  90.     /**
  91.     * @var array An array of field ids that must exist in the table
  92.     * Not used yet
  93.     */
  94.     var $requiredParams = array('id', 'rootid', 'l', 'r', 'norder', 'level');
  95.     
  96.     /**
  97.     * @var string The table with the actual tree data
  98.     * @access public
  99.     */
  100.     var $node_table = 'tb_nodes';
  101.     
  102.     /**
  103.     * @var string The table to handle locking
  104.     * @access public
  105.     */
  106.     var $lock_table = 'tb_locks';
  107.     
  108.     /**
  109.     * @var string The table used for sequences
  110.     * @access public
  111.     */
  112.     var $sequence_table;
  113.     
  114.     /**
  115.     * Secondary order field.  Normally this is the order field, but can be changed to
  116.     * something else (i.e. the name field so that the tree can be shown alphabetically)
  117.     * @var string
  118.     * @access public
  119.     */
  120.     var $secondarySort;
  121.     
  122.     /**
  123.     * @var int The time to live of the lock
  124.     * @access public
  125.     */
  126.     var $lockTTL = 1;
  127.     
  128.     /**
  129.     * @var bool Enable debugging statements?
  130.     * @access public
  131.     */
  132.     var $debug = false;
  133.     
  134.     /**
  135.     * @var bool Lock the structure of the table?
  136.     * @access private
  137.     */
  138.     var $structureTableLock = false;
  139.     
  140.     /**
  141.     * @var bool Skip the callback events?
  142.     * @access private
  143.     */
  144.     var $skipCallbacks = false;
  145.     
  146.     /**
  147.     * @var object cache Optional PEAR::Cache object
  148.     * @access public
  149.     */
  150.     var $cache = false;
  151.     
  152.     /**
  153.     * @var bool Do we want to use caching
  154.     * @access private
  155.     */
  156.     var $_caching = false;
  157.     
  158.     /**
  159.     * 
  160.     * @var bool Temporary switch for cache
  161.     * @access private
  162.     */
  163.     var $_restcache = false;
  164.     
  165.     /**
  166.     * @var array Map of error messages to their descriptions
  167.     */
  168.     var $messages = array(
  169.     NESE_ERROR_RECURSION    => 'This operation would lead to a recursion',
  170.     NESE_ERROR_TBLOCKED     => 'The structure Table is locked for another database operation, please retry.',
  171.     NESE_DRIVER_NOT_FOUND   => 'The selected database driver wasn\'t found',
  172.     NESE_ERROR_NOTSUPPORTED => 'Method not supported yet',
  173.     NESE_ERROR_NOHANDLER    => 'Event handler not found',
  174.     NESE_ERROR_PARAM_MISSING=> 'Parameter missing',
  175.     NESE_MESSAGE_UNKNOWN    => 'Unknown error or message',
  176.     NESE_ERROR_NOT_FOUND    => 'Node not found', 
  177.     );
  178.     
  179.     /**
  180.     * @var array The array of event listeners
  181.     * @access public
  182.     */
  183.     var $eventListeners = array();
  184.     
  185.     // }}}
  186.     // +---------------------------------------+
  187.     // | Base methods                          |
  188.     // +---------------------------------------+
  189.     // {{{ constructor 
  190.     
  191.     
  192.  
  193.     
  194.     /**
  195.     * Constructor
  196.     *
  197.     * @param array $params Database column fields which should be returned
  198.     *
  199.     * @access private
  200.     * @return void
  201.     */
  202.     function DB_NestedSet($params)
  203.     {
  204.         $this->_debugMessage('DB_NestedSet()');
  205.         $this->PEAR();
  206.         if (is_array($params) && count($params) > 0) {
  207.             $this->params = $params;
  208.         }
  209.         
  210.         $this->flparams = array_flip($this->params);
  211.         $this->sequence_table = $this->node_table . '_' . $this->flparams['id'];
  212.         $this->secondarySort = $this->flparams['norder'];
  213.     }
  214.     
  215.     // }}}
  216.     // {{{ factory
  217.     
  218.     /**
  219.     * Handles the returning of a concrete instance of DB_NestedSet based on the driver.
  220.     *
  221.     * @param string $driver The driver, such as DB or MDB
  222.     * @param string $dsn The dsn for connecting to the database
  223.     * @param array $params The field name params for the node table
  224.     *
  225.     * @access public
  226.     * @return object The DB_NestedSet object
  227.     */
  228.     function & factory($driver, $dsn, $params = array())
  229.     {
  230.         $driverpath = dirname(__FILE__).'/NestedSet/'. $driver.'.php';
  231.         if(!file_exists($driverpath) || !$driver) {
  232.             return new PEAR_Error('E200',"The database driver '$driver' wasn't found");
  233.         }
  234.         
  235.         include_once($driverpath);
  236.         $classname = 'DB_NestedSet_' . $driver;
  237.         return new $classname($dsn, $params);
  238.     }
  239.     
  240.     // }}}
  241.     // {{{ destructor
  242.     
  243.     /**
  244.     * PEAR Destructor
  245.     * Releases all locks
  246.     * Closes open database connections
  247.     *
  248.     * @access private
  249.     * @return void
  250.     */
  251.     function _DB_NestedSet()
  252.     {
  253.         $this->_debugMessage('_DB_NestedSet()');
  254.         $this->_releaseLock();
  255.     }
  256.     
  257.     // }}}
  258.     // +----------------------------------------------+
  259.     // | NestedSet manipulation and query methods     |
  260.     // |----------------------------------------------+
  261.     // | Querying the tree                            |
  262.     // +----------------------------------------------+
  263.     // {{{ getAllNodes()
  264.     
  265.     /**
  266.     * Fetch the whole NestedSet
  267.     *
  268.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  269.     *             a set of DB_NestedSet_Node objects?
  270.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  271.     *             of the parameter keys, or leave them as is?
  272.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  273.     *
  274.     * @access public
  275.     * @return mixed False on error, or an array of nodes
  276.     */
  277.     function getAllNodes($keepAsArray = false, $aliasFields = true, $addSQL = array())
  278.     {
  279.         $this->_debugMessage('getAllNodes()');
  280.         $sql = sprintf('SELECT %s %s FROM %s %s %s ORDER BY %s.%s, %s.%s ASC', 
  281.                 $this->_getSelectFields($aliasFields),
  282.                 $this->_addSQL($addSQL, 'cols'),
  283.                 $this->node_table,
  284.                 $this->_addSQL($addSQL, 'join'),
  285.                 $this->_addSQL($addSQL, 'append'),
  286.                 $this->node_table,
  287.                 $this->flparams['level'],
  288.                 $this->node_table,
  289.                 $this->secondarySort);
  290.         
  291.         if (!$this->_caching) {
  292.             $nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
  293.         } else {
  294.             $nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
  295.         }
  296.         
  297.         // EVENT (nodeLoad)
  298.         foreach (array_keys($nodeSet) as $key) {
  299.             $this->triggerEvent('nodeLoad', $nodeSet[$key]);
  300.         }
  301.         return $nodeSet;
  302.     }
  303.     
  304.     // }}}
  305.     // {{{ getRootNodes()
  306.     
  307.     /**
  308.     * Fetches the first level (the rootnodes) of the NestedSet
  309.     *
  310.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  311.     *             a set of DB_NestedSet_Node objects?
  312.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  313.     *             of the parameter keys, or leave them as is?
  314.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  315.     *
  316.     * @see _addSQL()
  317.     * @access public
  318.     * @return mixed False on error, or an array of nodes
  319.     */
  320.     function getRootNodes($keepAsArray = false, $aliasFields = true, $addSQL = array())
  321.     {
  322.         $this->_debugMessage('getRootNodes()');
  323.         $sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s.%s %s ORDER BY %s.%s ASC', 
  324.                 $this->_getSelectFields($aliasFields),
  325.                 $this->_addSQL($addSQL, 'cols'),
  326.                 $this->node_table,
  327.                 $this->_addSQL($addSQL, 'join'),
  328.                 $this->node_table,
  329.                 $this->flparams['id'],
  330.                 $this->node_table,
  331.                 $this->flparams['rootid'],
  332.                 $this->_addSQL($addSQL, 'append'),
  333.                 $this->node_table,
  334.                 $this->secondarySort);
  335.  
  336.         if (!$this->_caching) {
  337.             $nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
  338.         } else {
  339.             $nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
  340.         }
  341.         
  342.         // EVENT (nodeLoad)
  343.         foreach (array_keys($nodeSet) as $key) {
  344.             $this->triggerEvent('nodeLoad', $nodeSet[$key]);
  345.         }
  346.         return $nodeSet;
  347.     }
  348.     
  349.     // }}}
  350.     // {{{ getBranch()
  351.     
  352.     /**
  353.     * Fetch the whole branch where a given node id is in
  354.     *
  355.     * @param int  $id The node ID
  356.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  357.     *             a set of DB_NestedSet_Node objects?
  358.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  359.     *             of the parameter keys, or leave them as is?
  360.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  361.     *
  362.     * @see _addSQL()
  363.     * @access public
  364.     * @return mixed False on error, or an array of nodes
  365.     */
  366.     function getBranch($id, $keepAsArray = false, $aliasFields = true, $addSQL = array())
  367.     {
  368.         $this->_debugMessage('getBranch($id)');
  369.         if (!($thisnode = $this->_getNodeObject($id))) {
  370.             return false;
  371.         }
  372.         
  373.         $sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s ORDER BY %s.%s, %s.%s ASC', 
  374.                 $this->_getSelectFields($aliasFields),
  375.                 $this->_addSQL($addSQL, 'cols'),
  376.                 $this->node_table,
  377.                 $this->_addSQL($addSQL, 'join'),
  378.                 $this->node_table,
  379.                 $this->flparams['rootid'],
  380.                 $this->db->quote($thisnode->rootid),
  381.                 $this->_addSQL($addSQL, 'append'),
  382.                 $this->node_table,
  383.                 $this->flparams['level'],
  384.                 $this->node_table,
  385.                 $this->secondarySort);
  386.  
  387.         if (!$this->_caching) {
  388.             $nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
  389.         } else {
  390.             $nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
  391.         }
  392.         
  393.         // EVENT (nodeLoad)
  394.         foreach (array_keys($nodeSet) as $key) {
  395.             $this->triggerEvent('nodeLoad', $nodeSet[$key]);
  396.         }
  397.         return $nodeSet;
  398.     }
  399.     
  400.     // }}}
  401.     // {{{ getParents()
  402.     
  403.     /**
  404.      * Fetch the parents of a node given by id
  405.      *
  406.      * @param int  $id The node ID
  407.      * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  408.      *             a set of DB_NestedSet_Node objects?
  409.      * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  410.      *             of the parameter keys, or leave them as is?
  411.      * @param array $addSQL (optional) Array of additional params to pass to the query.
  412.      *
  413.      * @see _addSQL()
  414.      * @access public
  415.      * @return mixed False on error, or an array of nodes
  416.      */
  417.     function getParents($id, $keepAsArray = false, $aliasFields = true, $addSQL = array())
  418.     {
  419.         $this->_debugMessage('getParents($id)');
  420.         if (!($child = $this->_getNodeObject($id))) {
  421.             return false;
  422.         }
  423.         
  424.         $sql = sprintf('SELECT %s %s FROM %s %s 
  425.                         WHERE %s.%s=%s AND %s.%s<%s AND %s.%s<%s AND %s.%s>%s %s 
  426.                         ORDER BY %s.%s ASC', 
  427.                         $this->_getSelectFields($aliasFields),
  428.                         $this->_addSQL($addSQL, 'cols'),
  429.                         $this->node_table,
  430.                         $this->_addSQL($addSQL, 'join'),
  431.                         $this->node_table,
  432.                         $this->flparams['rootid'],
  433.                         $child->rootid,
  434.                         $this->node_table,
  435.                         $this->flparams['level'],
  436.                         $child->level,
  437.                         $this->node_table,
  438.                         $this->flparams['l'],
  439.                         $child->l,
  440.                         $this->node_table,
  441.                         $this->flparams['r'],
  442.                         $child->r,
  443.                         $this->_addSQL($addSQL, 'append'),
  444.                         $this->node_table,
  445.                         $this->flparams['level']);
  446.  
  447.         if (!$this->_caching) {
  448.             $nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
  449.         } else {
  450.             $nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
  451.         }
  452.         
  453.         // EVENT (nodeLoad)
  454.         foreach (array_keys($nodeSet) as $key) {
  455.             $this->triggerEvent('nodeLoad', $nodeSet[$key]);
  456.         }
  457.         return $nodeSet;
  458.     }
  459.     
  460.     // }}}
  461.     // {{{ getChildren()
  462.     
  463.     /**
  464.      * Fetch the children _one level_ after of a node given by id
  465.      *
  466.      * @param int  $id The node ID
  467.      * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  468.      *             a set of DB_NestedSet_Node objects?
  469.      * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  470.      *             of the parameter keys, or leave them as is?
  471.      * @param bool $forceNorder (optional) Force the result to be ordered by the norder
  472.      *             param (as opposed to the value of secondary sort).  Used by the move and
  473.      *             add methods.
  474.      * @param array $addSQL (optional) Array of additional params to pass to the query.
  475.      *
  476.      * @see _addSQL()
  477.      * @access public
  478.      * @return mixed False on error, or an array of nodes
  479.      */
  480.     function getChildren($id, $keepAsArray = false, $aliasFields = true, $forceNorder = false, $addSQL = array())
  481.     {
  482.         $this->_debugMessage('getChildren($id)');
  483.         $parent = $this->_getNodeObject($id);
  484.         if (!$parent || $parent->l == ($parent->r - 1)) {
  485.             return false;
  486.         }
  487.         
  488.         $orderBy = $forceNorder ? $this->flparams['norder'] : $this->secondarySort;
  489.         $sql = sprintf('SELECT %s %s FROM %s %s 
  490.                         WHERE %s.%s=%s AND %s.%s=%s+1 AND %s.%s BETWEEN %s AND %s %s 
  491.                         ORDER BY %s.%s ASC',
  492.                         $this->_getSelectFields($aliasFields),
  493.                         $this->_addSQL($addSQL, 'cols'),
  494.                         $this->node_table,
  495.                         $this->_addSQL($addSQL, 'join'),
  496.                         $this->node_table,
  497.                         $this->flparams['rootid'],
  498.                         $this->db->quote($parent->rootid),
  499.                         $this->node_table,
  500.                         $this->flparams['level'],
  501.                         $parent->level,
  502.                         $this->node_table,
  503.                         $this->flparams['l'],
  504.                         $parent->l,
  505.                         $parent->r,
  506.                         $this->_addSQL($addSQL, 'append'),
  507.                         $this->node_table,
  508.                         $orderBy);
  509.  
  510.         if (!$this->_caching) {
  511.             $nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
  512.         } else {
  513.             $nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
  514.         }
  515.         
  516.         // EVENT (nodeLoad)
  517.         foreach (array_keys($nodeSet) as $key) {
  518.             $this->triggerEvent('nodeLoad', $nodeSet[$key]);
  519.         }
  520.         return $nodeSet;
  521.     }
  522.     
  523.     // }}}
  524.     // {{{ getSubBranch()
  525.     
  526.     /**
  527.     * Fetch all the children of a node given by id
  528.     *
  529.     * getChildren only queries the immediate children
  530.     * getSubBranch returns all nodes below the given node
  531.     *
  532.     * @param string  $id The node ID
  533.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  534.     *             a set of DB_NestedSet_Node objects?
  535.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  536.     *             of the parameter keys, or leave them as is?
  537.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  538.     *
  539.     * @see _addSQL()
  540.     * @access public
  541.     * @return mixed False on error, or an array of nodes
  542.     */
  543.     function getSubBranch($id, $keepAsArray = false, $aliasFields = true, $addSQL = array())
  544.     {
  545.         $this->_debugMessage('getSubBranch($id)');
  546.         if (!($parent = $this->_getNodeObject($id))) {
  547.             return false;
  548.         }
  549.         
  550.         $sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s BETWEEN %s AND %s AND %s.%s=%s AND %s.%s!=%s %s',
  551.                 $this->_getSelectFields($aliasFields),
  552.                 $this->_addSQL($addSQL, 'cols'),
  553.                 $this->node_table,
  554.                 $this->_addSQL($addSQL, 'join'),
  555.                 $this->node_table,
  556.                 $this->flparams['l'],
  557.                 $parent->l,
  558.                 $parent->r,
  559.                 $this->node_table,
  560.                 $this->flparams['rootid'],
  561.                 $this->db->quote($parent->rootid),
  562.                 $this->node_table,
  563.                 $this->flparams['id'],
  564.                 $this->db->quote($id),
  565.                 $this->_addSQL($addSQL, 'append'));
  566.  
  567.         if (!$this->_caching) {
  568.             $nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
  569.         } else {
  570.             $nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
  571.         }
  572.         
  573.         // EVENT (nodeLoad)
  574.         foreach (array_keys($nodeSet) as $key) {
  575.             $this->triggerEvent('nodeLoad', $nodeSet[$key]);
  576.         }
  577.         return $nodeSet;
  578.     }
  579.     
  580.     // }}}
  581.     // {{{ pickNode()
  582.     
  583.     /**
  584.     * Fetch the data of a node with the given id
  585.     *
  586.     * @param int  $id The node id of the node to fetch
  587.     * @param bool $keepAsArray (optional) Keep the result as an array or transform it into
  588.     *             a set of DB_NestedSet_Node objects?
  589.     * @param bool $aliasFields (optional) Should we alias the fields so they are the names
  590.     *             of the parameter keys, or leave them as is?
  591.     * @param string $idfield (optional) Which field has to be compared with $id?
  592.     *               This is can be used to pick a node by other values (e.g. it's name).
  593.     * @param array $addSQL (optional) Array of additional params to pass to the query.
  594.     *
  595.     * @see _addSQL()
  596.     * @access public
  597.     * @return mixed False on error, or an array of nodes
  598.     */
  599.     function pickNode($id, $keepAsArray = false, $aliasFields = true, $idfield = 'id', $addSQL = array())
  600.     {
  601.         $this->_debugMessage('pickNode($id)');
  602.         if (is_object($id) && $id->id) {
  603.             $id = $id->id;
  604.         }
  605.         
  606.         $sql = sprintf('SELECT %s %s FROM %s %s WHERE %s.%s=%s %s',
  607.                 $this->_getSelectFields($aliasFields),
  608.                 $this->_addSQL($addSQL, 'cols'),
  609.                 $this->node_table,
  610.                 $this->_addSQL($addSQL, 'join'),
  611.                 $this->node_table,
  612.                 $this->flparams[$idfield],
  613.                 $this->db->quote($id),
  614.                 $this->_addSQL($addSQL, 'append'));
  615.  
  616.         if (!$this->_caching) {
  617.             $nodeSet = $this->_processResultSet($sql, $keepAsArray, $aliasFields);
  618.         } else {
  619.             $nodeSet = $this->cache->call('DB_NestedSet->_processResultSet', $sql, $keepAsArray, $aliasFields);
  620.         }
  621.         
  622.         $nsKey = false;
  623.         // EVENT (nodeLoad)
  624.         foreach (array_keys($nodeSet) as $key) {
  625.             $this->triggerEvent('nodeLoad', $nodeSet[$key]);
  626.             $nsKey = $key;
  627.         }
  628.         
  629.         if (is_array($nodeSet) && $idfield != 'id') {
  630.             $id = $nsKey;
  631.         }
  632.         
  633.         return isset($nodeSet[$id]) ? $nodeSet[$id] : false;
  634.     }
  635.     
  636.     // }}}
  637.     // {{{ isParent()
  638.     
  639.     /**
  640.      * See if a given node is a parent of another given node
  641.      *
  642.      * A node is considered to be a parent if it resides above the child
  643.      * So it doesn't mean that the node has to be an immediate parent.
  644.      * To get this information simply compare the levels of the two nodes
  645.      * after you know that you have a parent relation.
  646.      *
  647.      * @param mixed  $parent The parent node as array or object
  648.      * @param mixed  $child  The child node as array or object
  649.      *
  650.      * @access public
  651.      * @return bool True if it's a parent
  652.      */
  653.     function isParent($parent, $child) {
  654.         
  655.         $this->_debugMessage('isParent($parent, $child)');
  656.         
  657.         if (!isset($parent)|| !isset($child)) {
  658.             return false;
  659.         }
  660.         
  661.         if (is_array($parent)) {
  662.             $p_rootid     = $parent['rootid'];
  663.             $p_l        = $parent['l'];
  664.             $p_r        = $parent['r'];
  665.             
  666.         } elseif (is_object($parent)) {
  667.             $p_rootid     = $parent->rootid;
  668.             $p_l        = $parent->l;
  669.             $p_r        = $parent->r;
  670.         }
  671.         
  672.         if (is_array($child)) {
  673.             $c_rootid     = $child['rootid'];
  674.             $c_l        = $child['l'];
  675.             $c_r        = $child['r'];
  676.         } elseif (is_object($child)) {
  677.             $c_rootid     = $child->rootid;
  678.             $c_l        = $child->l;
  679.             $c_r        = $child->r;
  680.         }
  681.         
  682.         if (($p_rootid == $c_rootid) && ($p_l < $c_l && $p_r > $c_r)) {
  683.             return true;
  684.         }
  685.         
  686.         return false;
  687.     }
  688.     
  689.     // }}}
  690.     // {{{ _processResultSet()
  691.     
  692.     /**
  693.     * Processes a DB result set by checking for a DB error and then transforming the result
  694.     * into a set of DB_NestedSet_Node objects or leaving it as an array.
  695.     *
  696.     * @param string $sql The sql query to be done
  697.     * @param bool $keepAsArray Keep the result as an array or transform it into a set of
  698.     *             DB_NestedSet_Node objects?
  699.     * @param bool $fieldsAreAliased Are the fields aliased?
  700.     *
  701.     * @access    private
  702.     * @return mixed False on error or the transformed node set.
  703.     */
  704.     function _processResultSet($sql, $keepAsArray, $fieldsAreAliased)
  705.     {
  706.         $result = $this->db->getAll($sql);
  707.         if ($this->_testFatalAbort($result, __FILE__, __LINE__)) {
  708.             return false;
  709.         }
  710.         
  711.         $nodes = array();
  712.         $idKey = $fieldsAreAliased ? 'id' : $this->flparams['id'];
  713.         foreach ($result as $row) {
  714.             $node_id = $row[$idKey];
  715.             if ($keepAsArray) {
  716.                 $nodes[$node_id] = $row;
  717.             } else {
  718.                 // Create an instance of the node container
  719.                 $nodes[$node_id] =& new DB_NestedSet_Node($row);
  720.             }
  721.             
  722.         }
  723.         
  724.         return $nodes;
  725.     }
  726.     
  727.     // }}}
  728.     // {{{ _getNodeObject()
  729.     
  730.     /**
  731.     * Gets the node to work on based upon an id
  732.     *
  733.     * @param mixed $id The id which can be an object or integer
  734.     *
  735.     * @access private
  736.     * @return mixed The node object for an id or false on error
  737.     */
  738.     function _getNodeObject($id)
  739.     {
  740.         if (!is_object($id) || !$id->id) {
  741.             return $this->pickNode($id);
  742.         }
  743.         else {
  744.             return $id;
  745.         }
  746.     }
  747.     
  748.     // }}}
  749.     // {{{ _addSQL()
  750.  
  751.     /**
  752.      * Adds a specific type of SQL to a query string
  753.      *
  754.      * @param array $addSQL The array of SQL strings to add.  Example value:
  755.      *               $addSQL = array(
  756.      *               'cols' => 'tb2.col2, tb2.col3',         // Additional tables/columns
  757.      *               'join' => 'LEFT JOIN tb1 USING(STRID)', // Join statement
  758.      *               'append' => 'GROUP by tb1.STRID');      // Group condition
  759.      * @param string $type The type of SQL.  Can be 'cols', 'join', or 'append'.
  760.      *
  761.      * @access private
  762.      * @return string The SQL, properly formatted
  763.      */
  764.     function _addSQL($addSQL, $type) 
  765.     {
  766.         if (!isset($addSQL[$type])) {
  767.             return '';
  768.         }        
  769.         
  770.         switch($type) {
  771.             case 'cols':
  772.                 return ', ' . $addSQL[$type];
  773.             break;    
  774.             default:
  775.                 return $addSQL[$type];
  776.             break;
  777.         }
  778.     }
  779.  
  780.     // }}}
  781.     // {{{ _getSelectFields()
  782.     
  783.     /**
  784.     * Gets the select fields based on the params
  785.     *
  786.     * @param bool $aliasFields Should we alias the fields so they are the names of the
  787.     *             parameter keys, or leave them as is?
  788.     *
  789.     * @access private
  790.     * @return string A string of query fields to select
  791.     */
  792.     function _getSelectFields($aliasFields)
  793.     {
  794.         $queryFields = array();
  795.         foreach ($this->params as $key => $val) {
  796.             $tmp_field = $this->node_table . '.' . $key;
  797.             if ($aliasFields) {
  798.                 $tmp_field .= ' AS ' . $val;
  799.             }
  800.             $queryFields[] = $tmp_field;
  801.         }
  802.  
  803.         $fields = implode(', ', $queryFields);
  804.         return $fields;
  805.     }
  806.     
  807.     // }}}
  808.     // +----------------------------------------------+
  809.     // | NestedSet manipulation and query methods     |
  810.     // |----------------------------------------------+
  811.     // | insert / delete / update of nodes            |
  812.     // +----------------------------------------------+
  813.     // | [PUBLIC]                                     |
  814.     // +----------------------------------------------+
  815.     // {{{ createRootNode()
  816.     
  817.     /**
  818.     * Creates a new root node
  819.     * Optionally it deletes the whole tree and creates one initial rootnode
  820.     *
  821.     * <pre>
  822.     * +-- root1 [target]
  823.     * |
  824.     * +-- root2 [new]
  825.     * |
  826.     * +-- root3
  827.     * </pre>
  828.     *
  829.     * @param array    $values      Hash with param => value pairs of the node (see $this->params)
  830.     * @param integer  $id          ID of target node (the rootnode after which the node should be inserted)
  831.     * @param bool     $first       Danger: Deletes and (re)init's the hole tree - sequences are reset
  832.     *
  833.     * @access public
  834.     * @return int The node id
  835.     */
  836.     function createRootNode($values, $id = false, $first = false)
  837.     {
  838.         $this->_debugMessage('createRootNode($values, $id = false, $first = false)');
  839.         // Try to aquire a table lock
  840.         if(PEAR::isError($lock=$this->_setLock())) {
  841.             return $lock;
  842.         }
  843.         
  844.         $flft = $this->flparams['l'];
  845.         $frgt = $this->flparams['r'];
  846.         $froot = $this->flparams['rootid'];
  847.         $fid = $this->flparams['id'];
  848.         $freh = $this->flparams['norder'];
  849.         $flevel = $this->flparams['level'];
  850.         $tb = $this->node_table;
  851.         $addval = array();
  852.         $addval[$flevel] = 1;
  853.         // Shall we delete the existing tree (reinit)
  854.         if ($first) {
  855.             $sql = "DELETE FROM $tb";
  856.             $this->db->query($sql);
  857.             $this->db->dropSequence($this->sequence_table);
  858.             // New order of the new node will be 1
  859.             $addval[$freh] = 1;
  860.         } else {
  861.             // Let's open a gap for the new node
  862.             $parent = $this->pickNode($id);
  863.             if (!$parent) {
  864.                 // invalid parent node, order will be 1
  865.                 $addval[$freh] = 1;
  866.                 // no gap to make
  867.                 $first = true;
  868.             }
  869.             else {
  870.                 $addval[$freh] = $parent->norder + 1;
  871.             }
  872.         }
  873.         
  874.         // Sequence of node id (equals to root id in this case
  875.         $addval[$froot] = $node_id = $addval[$fid] = $this->db->nextId($this->sequence_table);
  876.         // Left/Right values for rootnodes
  877.         $addval[$flft] = 1;
  878.         $addval[$frgt] = 2;
  879.         // Transform the node data hash to a query
  880.         if (!$qr = $this->_values2Query($values, $addval)) {
  881.             return false;
  882.         }
  883.         
  884.         if (!$first) {
  885.             // Open the gap
  886.             $sql = "UPDATE $tb SET $freh=$freh+1 WHERE $fid=$froot AND $freh>$parent->norder";
  887.             $res = $this->db->query($sql);
  888.             $this->_testFatalAbort($res, __FILE__,  __LINE__);
  889.         }
  890.         
  891.         // Insert the new node
  892.         $sql = "INSERT INTO $tb SET $qr";
  893.         $res = $this->db->query($sql);
  894.         $this->_testFatalAbort($res, __FILE__,  __LINE__);
  895.         
  896.         // EVENT (nodeCreate)
  897.         $thisnode = &$this->pickNode($node_id);
  898.         $this->triggerEvent('nodeCreate', $thisnode);
  899.         return $node_id;
  900.     }
  901.     
  902.     // }}}
  903.     // {{{ createSubNode()
  904.     
  905.     /**
  906.     * Creates a subnode
  907.     *
  908.     * <pre>
  909.     * +-- root1
  910.     * |
  911.     * +-\ root2 [target]
  912.     * | |
  913.     * | |-- subnode1 [new]
  914.     * |
  915.     * +-- root3
  916.     * </pre>
  917.     *
  918.     * @param integer    $id          Parent node ID
  919.     * @param array      $values      Hash with param => value pairs of the node (see $this->params)
  920.     *
  921.     * @access public
  922.     * @return mixed The node id or false on error
  923.     */
  924.     function createSubNode($id, $values)
  925.     {
  926.         $this->_debugMessage('createSubNode($id, $values)');
  927.         // Try to aquire a table lock
  928.         if(PEAR::isError($lock = $this->_setLock())) {
  929.             return $lock;
  930.         }
  931.         
  932.         $freh = $this->flparams['norder'];
  933.         $flevel = $this->flparams['level'];
  934.         // Get the children of the target node
  935.         $children = $this->getChildren($id, false, true, true);
  936.         // We have children here
  937.         if ($children) {
  938.             // Get the last child
  939.             $last = array_pop($children);
  940.             // What we have to do is virtually an insert of a node after the last child
  941.             // So we don't have to proceed creating a subnode
  942.             $newNode =& $this->createRightNode($last->id, $values);
  943.             return $newNode;
  944.         }
  945.         
  946.         // invalid parent id, bail out
  947.         if (!($thisnode = $this->pickNode($id))) {
  948.             $this->raiseError("Parent id: $id not found", NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR);
  949.             return false;
  950.         }
  951.         
  952.         $flft = $this->flparams['l'];
  953.         $frgt = $this->flparams['r'];
  954.         $froot = $this->flparams['rootid'];
  955.         $fid = $this->flparams['id'];
  956.         $lft = $thisnode->l;
  957.         $rgt = $thisnode->r;
  958.         $rootid = $thisnode->rootid;
  959.         $plevel = $thisnode->level;
  960.         $tb = $this->node_table;
  961.         
  962.         // Open the gap
  963.         $sql = "UPDATE $tb SET $flft=$flft+2
  964.                 WHERE $froot=" . $this->db->quote($rootid) . " AND 
  965.                 $flft>" . $this->db->quote($rgt) . " AND 
  966.                 $frgt>=" . $this->db->quote($rgt);
  967.         $res = $this->db->query($sql);
  968.         $this->_testFatalAbort($res, __FILE__,  __LINE__);
  969.         
  970.         $sql = "UPDATE $tb SET $frgt=$frgt+2
  971.                 WHERE $froot=" . $this->db->quote($rootid) . " AND 
  972.                 $frgt>=" . $this->db->quote($rgt);
  973.         $res = $this->db->query($sql);
  974.         $this->_testFatalAbort($res, __FILE__,  __LINE__);
  975.         
  976.         $addval = array();
  977.         $addval[$flft] = $rgt;
  978.         $addval[$frgt] = $rgt + 1;
  979.         $addval[$froot] = $rootid;
  980.         $addval[$freh] = 1;
  981.         $addval[$flevel] = $plevel + 1;
  982.         $node_id = $addval[$fid] = $this->db->nextId($this->sequence_table);
  983.         if (!$qr = $this->_values2Query($values, $addval)) {
  984.             return false;
  985.         }
  986.         
  987.         $sql = "INSERT INTO $tb SET $qr";
  988.         $res = $this->db->query($sql);
  989.         $this->_testFatalAbort($res, __FILE__,  __LINE__);
  990.         
  991.         // EVENT (NodeCreate)
  992.         $thisnode = $this->pickNode($node_id);
  993.         $this->triggerEvent('nodeCreate', $thisnode);
  994.         return $node_id;
  995.     }
  996.     
  997.     // }}}
  998.     // {{{ createRightNode()
  999.     
  1000.     /**
  1001.     * Creates a node after a given node
  1002.     * <pre>
  1003.     * +-- root1
  1004.     * |
  1005.     * +-\ root2
  1006.     * | |
  1007.     * | |-- subnode1 [target]
  1008.     * | |-- subnode2 [new]
  1009.     * | |-- subnode3
  1010.     * |
  1011.     * +-- root3
  1012.     * </pre>
  1013.     *
  1014.     * @param int   $target        Target node ID
  1015.     * @param array      $values      Hash with param => value pairs of the node (see $this->params)
  1016.     *
  1017.     * @access public
  1018.     * @return object The new node object
  1019.     */
  1020.     function createRightNode($target, $values)
  1021.     {
  1022.         $this->_debugMessage('createRightNode($target, $values)');
  1023.         if(PEAR::isError($lock=$this->_setLock())) {
  1024.             return $lock;
  1025.         }
  1026.         
  1027.         $id = $target;
  1028.         $flft = $this->flparams['l'];
  1029.         $frgt = $this->flparams['r'];
  1030.         $froot = $this->flparams['rootid'];
  1031.         $freh = $this->flparams['norder'];
  1032.         $fid = $this->flparams['id'];
  1033.         $flevel = $this->flparams['level'];
  1034.         // invalid target node, bail out
  1035.         if (!($thisnode = $this->pickNode($id))) {
  1036.             $this->raiseError("Target id: $id not found", NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR);
  1037.             return false;
  1038.         }
  1039.         
  1040.         // If the target node is a rootnode we virtually want to create a new root node
  1041.         if ($thisnode->rootid == $thisnode->id) {
  1042.             return $this->createRootNode($values, $id);
  1043.         }
  1044.         
  1045.         $lft = $thisnode->l;
  1046.         $rgt = $thisnode->r;
  1047.         $rootid = $thisnode->rootid;
  1048.         $level = $thisnode->level;
  1049.         $parent_order = $thisnode->norder;
  1050.         $tb = $this->node_table;
  1051.         $addval = array();
  1052.         $parents = $this->getParents($id);
  1053.         $parent = array_pop($parents);
  1054.         $plft = $parent->l;
  1055.         $prgt = $parent->r;
  1056.         // Open the gap within the current level
  1057.         $sql = "UPDATE $tb SET $freh=$freh+1
  1058.                 WHERE $froot=" . $this->db->quote($rootid) . " AND 
  1059.                 $freh>$parent_order AND 
  1060.                 $flevel=$level AND 
  1061.                 $flft BETWEEN $plft AND $prgt";
  1062.                
  1063.         $res = $this->db->query($sql);
  1064.         $this->_testFatalAbort($res, __FILE__,  __LINE__);
  1065.         
  1066.         // Update all nodes which have dependent left and right values
  1067.         $sql = "UPDATE $tb SET
  1068.                 $flft=IF($flft>$rgt, $flft+2, $flft),
  1069.                 $frgt=IF($frgt>$rgt, $frgt+2, $frgt)
  1070.                 WHERE $froot=" . $this->db->quote($rootid) . "
  1071.                 AND $frgt>" . $this->db->quote($rgt);
  1072.         $res = $this->db->query($sql);
  1073.         $this->_testFatalAbort($res, __FILE__,  __LINE__);
  1074.         
  1075.         $addval[$freh] = $parent_order + 1;
  1076.         $addval[$flft] = $rgt + 1;
  1077.         $addval[$frgt] = $rgt + 2;
  1078.         $addval[$froot] = $rootid;
  1079.         $addval[$flevel] = $level;
  1080.         $node_id = $addval[$fid] = $this->db->nextId($this->sequence_table);
  1081.         if (!$qr = $this->_values2Query($values, $addval)) {
  1082.             return false;
  1083.         }
  1084.         
  1085.         // Insert the new node
  1086.         $sql = "INSERT INTO $tb SET $qr";
  1087.         $res = $this->db->query($sql);
  1088.         $this->_testFatalAbort($res, __FILE__,  __LINE__);
  1089.         
  1090.         // EVENT (NodeCreate)
  1091.         $thisnode =& $this->pickNode($node_id);
  1092.         $this->triggerEvent('nodeCreate', $thisnode);
  1093.         return $node_id;
  1094.     }
  1095.     
  1096.     // }}}
  1097.     // {{{ deleteNode()
  1098.     
  1099.     /**
  1100.     * Deletes a node
  1101.     *
  1102.     * @param int $id ID of the node to be deleted
  1103.     *
  1104.     * @access public
  1105.     * @return bool True if the delete succeeds
  1106.     */
  1107.     function deleteNode($id)
  1108.     {
  1109.         $this->_debugMessage('deleteNode($id)');
  1110.         if (PEAR::isError($lock = $this->_setLock())) {
  1111.             return $lock;
  1112.         }
  1113.         
  1114.         if (!($thisnode = $this->pickNode($id))) {
  1115.             return false;
  1116.         }
  1117.         
  1118.         // EVENT (NodeDelete)
  1119.         $this->triggerEvent('nodeDelete', $thisnode);
  1120.         
  1121.         $parents = $this->getParents($id);
  1122.         $parent = array_pop($parents);
  1123.         $plft = $parent->l;
  1124.         $prgt = $parent->r;
  1125.                 
  1126.         $tb = $this->node_table;
  1127.         $flft = $this->flparams['l'];
  1128.         $frgt = $this->flparams['r'];
  1129.         $fid = $this->flparams['id'];
  1130.         $froot = $this->flparams['rootid'];
  1131.         $freh = $this->flparams['norder'];
  1132.         $flevel = $this->flparams['level'];
  1133.         $lft = $thisnode->l;
  1134.         $rgt = $thisnode->r;
  1135.         $order = $thisnode->norder;
  1136.         $level = $thisnode->level;
  1137.         $rootid = $thisnode->rootid;
  1138.         $len = $rgt - $lft + 1;
  1139.         // Delete the node
  1140.         $sql = "DELETE from $tb WHERE $flft BETWEEN $lft AND $rgt AND $froot=" . $this->db->quote($rootid);
  1141.         $this->db->query($sql);
  1142.         
  1143.         if ($thisnode->id != $thisnode->rootid) {
  1144.             // The node isn't a rootnode so close the gap
  1145.             $sql = "UPDATE $tb SET
  1146.                     $flft=IF($flft>$lft, $flft-$len, $flft),
  1147.                     $frgt=IF($frgt>$lft, $frgt-$len, $frgt)
  1148.                     WHERE $froot=" . $this->db->quote($rootid) . " AND 
  1149.                     ($flft>$lft OR $frgt>$rgt)";
  1150.             $res = $this->db->query($sql);
  1151.             $this->_testFatalAbort($res, __FILE__,  __LINE__);
  1152.             
  1153.             // Re-order
  1154.             $sql = "UPDATE $tb SET $freh=$freh-1
  1155.                     WHERE $froot=" . $this->db->quote($rootid) . " AND 
  1156.                     $flevel=$level AND 
  1157.                     $freh>$order AND
  1158.                     $flft BETWEEN $plft AND $prgt";
  1159.  
  1160.             $res = $this->db->query($sql);
  1161.             $this->_testFatalAbort($res, __FILE__,  __LINE__);
  1162.         } else {
  1163.             // A rootnode was deleted and we only have to close the gap inside the order
  1164.             $sql = "UPDATE $tb SET $freh=$freh-1 WHERE $froot=$fid AND $freh > $order";
  1165.             $res = $this->db->query($sql);
  1166.             $this->_testFatalAbort($res, __FILE__,  __LINE__);
  1167.         }
  1168.         
  1169.         return true;
  1170.     }
  1171.     
  1172.     // }}}
  1173.     // {{{ updateNode()
  1174.     
  1175.     /**
  1176.     * Changes the payload of a node
  1177.     *
  1178.     * @param int    $id Node ID
  1179.     * @param array  $values Hash with param => value pairs of the node (see $this->params)
  1180.     *
  1181.     * @access public
  1182.     * @return bool True if the update is successful
  1183.     */
  1184.     function updateNode($id, $values)
  1185.     {
  1186.         $this->_debugMessage('updateNode($id, $values)');
  1187.         if (PEAR::isError($lock = $this->_setLock())) {
  1188.             return $lock;
  1189.         }
  1190.         
  1191.         if (!($thisnode =& $this->pickNode($id))) {
  1192.             return false;
  1193.         }
  1194.         
  1195.         $eparams = array('values' => $values);
  1196.         // EVENT (NodeUpdate)
  1197.         $this->triggerEvent('nodeUpdate', $thisnode, $eparams);
  1198.         $fid = $this->flparams['id'];
  1199.         $addvalues = array();
  1200.         if (!$qr = $this->_values2Query($values, $addvalues)) {
  1201.             return false;
  1202.         }
  1203.         
  1204.         $sql = "UPDATE $this->node_table SET $qr WHERE $fid=" . $this->db->quote($id);
  1205.         $res = $this->db->query($sql);
  1206.         $this->_testFatalAbort($res, __FILE__,  __LINE__);
  1207.         return true;
  1208.     }
  1209.     
  1210.     // }}}
  1211.     // +----------------------------------------------+
  1212.     // | Moving and copying                           |
  1213.     // |----------------------------------------------+
  1214.     // | [PUBLIC]                                     |
  1215.     // +----------------------------------------------+
  1216.     // {{{ moveTree()
  1217.     
  1218.     /**
  1219.     * Wrapper for node moving and copying
  1220.     *
  1221.     * @param int    $id Source ID
  1222.     * @param int    $target Target ID
  1223.     * @param array  $pos Position (use one of the NESE_MOVE_* constants)
  1224.     * @param bool   $copy Shall we create a copy
  1225.     *
  1226.     * @see _moveInsideLevel
  1227.     * @see _moveAcross
  1228.     * @see moveRoot2Root
  1229.     * @access public
  1230.     * @return int ID of the moved node or false on error
  1231.     */
  1232.     function moveTree($id, $target, $pos, $copy = false)
  1233.     {
  1234.         $this->_debugMessage('moveTree($id, $target, $pos, $copy = false)');
  1235.         if (PEAR::isError($lock = $this->_setLock())) {
  1236.             return $lock;
  1237.         }
  1238.  
  1239.         // This operations don't need callbacks except the copy handler
  1240.         // which ignores this setting
  1241.         $this->skipCallbacks = true;
  1242.         // Get information about source and target
  1243.         if (!($source = $this->pickNode($id))) {
  1244.             $this->raiseError("Node id: $id not found", NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR);
  1245.             return false;
  1246.         }
  1247.         
  1248.         if (!($target = $this->pickNode($target))) {
  1249.             $this->raiseError("Target id: $target not found", NESE_ERROR_NOT_FOUND, PEAR_ERROR_TRIGGER, E_USER_ERROR);
  1250.             return false;
  1251.         }
  1252.         
  1253.         // We have a recursion - let's stop
  1254.         if (($target->rootid == $source->rootid) &&
  1255.         (($source->l <= $target->l) &&
  1256.         ($source->r >= $target->r))) {
  1257.             
  1258.             return new PEAR_Error($this->_getMessage(NESE_ERROR_RECURSION),NESE_ERROR_RECURSION);
  1259.         }
  1260.         
  1261.         // Insert/move before or after
  1262.         if ($pos == NESE_MOVE_BEFORE || $pos == NESE_MOVE_AFTER) {
  1263.             if (($source->rootid == $source->id) &&
  1264.             ($target->rootid == $target->id) &&
  1265.             !$copy) {
  1266.                 // We have to move a rootnode which is different from moving inside a tree
  1267.                 return $this->moveRoot2Root($source, $target, $pos, $copy);
  1268.             }
  1269.             
  1270.             if (($source->rootid == $target->rootid) &&
  1271.             ($source->level == $target->level)) {
  1272.                 // We have to move inside the same subtree and inside the same level - no big deal
  1273.                 return $this->_moveInsideLevel($source, $target, $pos, $copy);
  1274.             }
  1275.         }
  1276.         
  1277.         // We have to move between different levels and maybe subtrees - let's rock ;)
  1278.         return $this->_moveAcross($source, $target, $pos, $copy);
  1279.     }
  1280.     
  1281.     // }}}
  1282.     // {{{ _moveAcross()
  1283.     
  1284.     /**
  1285.     * Moves nodes and trees to other subtrees or levels
  1286.     *
  1287.     * <pre>
  1288.     * [+] <--------------------------------+
  1289.     * +-[\] root1 [target]                 |
  1290.     *     <-------------------------+      |
  1291.     * +-\ root2                     |      |
  1292.     * | |                           |      |
  1293.     * | |-- subnode1 [target]       |      |B
  1294.     * | |-- subnode2 [new]          |S     |E
  1295.     * | |-- subnode3                |U     |F
  1296.     * |                             |B     |O
  1297.     * +-\ root3                     |      |R
  1298.     *   |-- subnode 3.1             |      |E
  1299.     *   |-\ subnode 3.2 [source] >--+------+
  1300.     *     |-- subnode 3.2.1
  1301.     *</pre>
  1302.     *
  1303.     * @param     object NodeCT $source   Source node
  1304.     * @param     object NodeCT $target   Target node
  1305.     * @param     string    $pos          Position [SUBnode/BEfore]
  1306.     * @param     bool         $copy                Shall we create a copy
  1307.     *
  1308.     * @access    private
  1309.     * @see        moveTree
  1310.     * @see        _r_moveAcross
  1311.     * @see        _moveCleanup
  1312.     */
  1313.     function _moveAcross($source, $target, $pos, $copy = false)
  1314.     {
  1315.         $this->_debugMessage('_moveAcross($source, $target, $pos, $copy = false)');
  1316.         if (PEAR::isError($lock = $this->_setLock())) {
  1317.             return $lock;
  1318.         }
  1319.         
  1320.         $tb = $this->node_table;
  1321.         $flft = $this->flparams['l'];
  1322.         $frgt = $this->flparams['r'];
  1323.         $fid = $this->flparams['id'];
  1324.         $froot = $this->flparams['rootid'];
  1325.         $freh = $this->flparams['norder'];
  1326.         $s_id = $source->id;
  1327.         $t_id = $target->id;
  1328.         $rootid = $target->rootid;
  1329.         // Get the current data from a node and exclude the id params which will be changed
  1330.         // because of the node move
  1331.         foreach($this->params as $key => $val) {
  1332.             if ($source->$val && !in_array($val, $this->requiredParams)) {
  1333.                 $values[$key] = trim($source->$val);
  1334.             }
  1335.         }
  1336.         
  1337.         if ($pos != NESE_MOVE_BELOW) {
  1338.             $c_id = $this->createRightNode($t_id, $values);
  1339.             $clone = $this->pickNode($c_id);
  1340.             if ($pos == NESE_MOVE_BEFORE) {
  1341.                 $this->moveTree($c_id, $t_id, $pos);
  1342.             }
  1343.         } else {
  1344.             $c_id = $this->createSubNode($t_id, $values);
  1345.             $clone = $this->pickNode($c_id);
  1346.         }
  1347.         
  1348.         $relations[$s_id] = $c_id;
  1349.  
  1350.         $children = $this->getChildren($source, false, true, true);
  1351.         $first = true;
  1352.         if ($children) {
  1353.             // Recurse trough the child nodes
  1354.             foreach($children AS $key => $val) {
  1355.                 if ($first) {
  1356.                     $first = false;
  1357.                     $previd = $this->_r_moveAcross($val, $clone, 'createSubNode', $relations);
  1358.                 } else {
  1359.                     $sister = $this->pickNode($previd);
  1360.                     $previd = $this->_r_moveAcross($val, $sister, 'createRightNode', $relations);
  1361.                 }
  1362.             }
  1363.         }
  1364.  
  1365.         $this->_moveCleanup($relations, $copy);
  1366.         if(!$copy) {
  1367.             return $source->id;
  1368.         } else {
  1369.             return $clone->id;
  1370.         }
  1371.     }
  1372.     
  1373.     // }}}
  1374.     // {{{ _r_moveAcross()
  1375.     
  1376.     /**
  1377.     * Recursion for _moveAcross
  1378.     *
  1379.     * @param     object     NodeCT $source    Source
  1380.     * @param     object     NodeCT $target    Target
  1381.     * @param     string    $action            createRightNode|createSubNode
  1382.     * @param     array    $relations        Hash $h[old ID]=new ID - maps the source node to the new created node (clone)
  1383.     * @access    private
  1384.     * @see        _moveAcross
  1385.     */
  1386.     function _r_moveAcross($source, $target, $action, &$relations) {
  1387.         $this->_debugMessage('_r_moveAcross($source, $target, $action, &$relations)');
  1388.         if (PEAR::isError($lock = $this->_setLock())) {
  1389.             return $lock;
  1390.         }
  1391.         
  1392.         foreach($this->params AS $key => $val) {
  1393.             if ($source->$val && !in_array($val, $this->requiredParams)) {
  1394.                 $values[$key] = trim($source->$val);
  1395.             }
  1396.         }
  1397.         
  1398.         $s_id = $source->id;
  1399.         $t_id = $target->id;
  1400.         $c_id = $this->$action($t_id, $values);
  1401.         $relations[$s_id] = $c_id;
  1402.         $children = $this->getChildren($source, false, true, true);
  1403.         if (!$children) {
  1404.             return $c_id;
  1405.         }
  1406.         
  1407.         $clone = $this->pickNode($c_id);
  1408.         $first = true;
  1409.         foreach($children as $key => $val) {
  1410.             if ($first) {
  1411.                 $first = false;
  1412.                 $previd =
  1413.                 $this->_r_moveAcross($val, $clone, 'createSubNode', $relations);
  1414.             } else {
  1415.                 $sister = $this->pickNode($previd);
  1416.                 $previd = $this->_r_moveAcross($val, $sister, 'createRightNode', $relations);
  1417.             }
  1418.         }
  1419.         
  1420.         return $c_id;
  1421.     }
  1422.     
  1423.     // }}}
  1424.     // {{{ _moveCleanup()
  1425.     
  1426.     /**
  1427.     * Deletes the old subtree (node) and writes the node id's into the cloned tree
  1428.     *
  1429.     *
  1430.     * @param     array    $relations        Hash in der Form $h[alteid]=neueid
  1431.     * @param     array    $copy                     Are we in copy mode?
  1432.     * @access    private
  1433.     */
  1434.     function _moveCleanup($relations, $copy = false)
  1435.     {
  1436.         $this->_debugMessage('_moveCleanup($relations, $copy = false)');
  1437.         if (PEAR::isError($lock = $this->_setLock())) {
  1438.             return $lock;
  1439.         }
  1440.         
  1441.         $tb = $this->node_table;
  1442.         $fid = $this->flparams['id'];
  1443.         $froot = $this->flparams['rootid'];
  1444.         foreach($relations AS $key => $val) {
  1445.             $clone = $this->pickNode($val);
  1446.             if ($copy) {
  1447.                 // EVENT (NodeCopy)
  1448.                 $thisnode =& $this->pickNode($key);
  1449.                 $eparams = array('clone' => $clone);
  1450.                 $this->triggerEvent('nodeCopy', $thisnode, $eparams);
  1451.                 continue;
  1452.             }
  1453.             
  1454.             // No callbacks here because the node itself doesn't get changed
  1455.             // Only it's position
  1456.             // If one needs a callback here please let me know
  1457.             $this->skipCallbacks = true;
  1458.             $this->deleteNode($key, true);
  1459.             // It's isn't a rootnode
  1460.             if ($clone->id != $clone->rootid) {
  1461.                 $u_values = array();
  1462.                 $u_id = $val;
  1463.                 $u_values[$fid] = $key;
  1464.                 $this->updateNode($u_id, $u_values);
  1465.             } else {
  1466.                 $sql = "UPDATE $tb SET
  1467.                             $fid=" . $this->db->quote($key) . ",
  1468.                             $froot=" . $this->db->quote($key) . " 
  1469.                         WHERE $fid=" . $this->db->quote($val);
  1470.                 $this->db->query($sql);
  1471.                 $orootid = $clone->rootid;
  1472.                 $sql = "UPDATE $tb
  1473.                         SET $froot=" . $this->db->quote($key) . "
  1474.                         WHERE $froot=" . $this->db->quote($orootid);
  1475.                 $this->db->query($sql);
  1476.             }
  1477.             
  1478.             $this->skipCallbacks = false;
  1479.         }
  1480.         
  1481.         return true;
  1482.     }
  1483.     
  1484.     // }}}
  1485.     // {{{ _moveInsideLevel()
  1486.     
  1487.     /**
  1488.     * Moves a node or subtree inside the same level
  1489.     *
  1490.     * <pre>
  1491.     * +-- root1
  1492.     * |
  1493.     * +-\ root2
  1494.     * | |
  1495.     * | |-- subnode1 [target]
  1496.     * | |-- subnode2 [new]
  1497.     * | |-- subnode3
  1498.     * |
  1499.     * +-\ root3
  1500.     *  [|]  <-----------------------+
  1501.     *   |-- subnode 3.1 [target]    |
  1502.     *   |-\ subnode 3.2 [source] >--+
  1503.     *     |-- subnode 3.2.1
  1504.     * </pre>
  1505.     *
  1506.     * @param     object NodeCT $source    Source
  1507.     * @param     object NodeCT $target    Target
  1508.     * @param     string $pos              BEfore | AFter
  1509.     * @param     string $copy             Copy mode?
  1510.     * @access    private
  1511.     * @see        moveTree
  1512.     */
  1513.     function _moveInsideLevel($source, $target, $pos, $copy = false)
  1514.     {
  1515.         $this->_debugMessage('_moveInsideLevel($source, $target, $pos, $copy = false)');
  1516.         if (PEAR::isError($lock=$this->_setLock())) {
  1517.             return $lock;
  1518.         }
  1519.         
  1520.         // If we only want to copy it's quite easy cause no gap will occur as in move mode
  1521.         if ($copy) {
  1522.             $parents = $this->getParents($target->id);
  1523.             $ntarget = @array_pop($parents);
  1524.             if (is_object($ntarget)) {
  1525.                 $npos = NESE_MOVE_BELOW;
  1526.             } else {
  1527.                 $npos = $pos;
  1528.                 $ntarget = $target;
  1529.             }
  1530.             
  1531.             // Let's move the node to it's destination
  1532.             $nroot = $this->_moveAcross($source, $ntarget, $npos, $copy);
  1533.             // Change the order
  1534.             return $this->moveTree($nroot, $target->id, $pos);
  1535.         }
  1536.         
  1537.         $parents = $this->getParents($source);
  1538.         $parent = array_pop($parents);
  1539.         $plft = $parent->l;
  1540.         $prgt = $parent->r;
  1541.         $tb = $this->node_table;
  1542.         $flft = $this->flparams['l'];
  1543.         $frgt = $this->flparams['r'];
  1544.         $fid = $this->flparams['id'];
  1545.         $froot = $this->flparams['rootid'];
  1546.         $freh = $this->flparams['norder'];
  1547.         $flevel = $this->flparams['level'];
  1548.         $s_order = $source->norder;
  1549.         $t_order = $target->norder;
  1550.         $level = $source->level;
  1551.         $rootid = $source->rootid;
  1552.         $s_id = $source->id;
  1553.         $t_id = $target->id;
  1554.         
  1555.         if ($s_order < $t_order) {
  1556.             if ($pos == NESE_MOVE_BEFORE) {
  1557.                 $sql = "UPDATE $tb SET $freh=$freh-1
  1558.                         WHERE $freh BETWEEN $s_order AND $t_order 
  1559.                             AND 
  1560.                             $fid!=$t_id
  1561.                             AND 
  1562.                             $fid!=$s_id
  1563.                             AND 
  1564.                             $flevel=" . $this->db->quote($level) . "
  1565.                             AND 
  1566.                             $froot=" . $this->db->quote($rootid) . " 
  1567.                             AND 
  1568.                             $flft BETWEEN $plft AND $prgt";
  1569.                 $res = $this->db->query($sql);
  1570.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1571.                 
  1572.                 $sql = "UPDATE $tb SET $freh=$t_order-1 WHERE $fid=$s_id";
  1573.                 $res = $this->db->query($sql);
  1574.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1575.             }
  1576.             elseif ($pos == NESE_MOVE_AFTER) {
  1577.                 $sql = "UPDATE $tb SET $freh=$freh-1
  1578.                         WHERE $freh BETWEEN $s_order AND $t_order 
  1579.                             AND 
  1580.                             $fid!=$s_id 
  1581.                             AND 
  1582.                             $flevel=" . $this->db->quote($level) . "
  1583.                             AND
  1584.                             $froot=" . $this->db->quote($rootid) . " 
  1585.                             AND 
  1586.                             $flft BETWEEN $plft AND $prgt";  
  1587.                 $res = $this->db->query($sql);
  1588.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1589.                 
  1590.                 $sql = "UPDATE $tb SET $freh=$t_order WHERE $fid = $s_id";
  1591.                 $res = $this->db->query($sql);
  1592.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1593.             }
  1594.         }
  1595.         
  1596.         if ($s_order > $t_order) {
  1597.             if ($pos == NESE_MOVE_BEFORE) {
  1598.                 $sql = "UPDATE $tb SET $freh=$freh+1
  1599.                         WHERE $freh BETWEEN $t_order AND $s_order 
  1600.                             AND 
  1601.                             $fid != $s_id
  1602.                             AND 
  1603.                             $froot=" . $this->db->quote($rootid) . "
  1604.                             AND 
  1605.                             $flevel=" . $this->db->quote($level) . "
  1606.                             AND 
  1607.                             $flft BETWEEN $plft AND $prgt";
  1608.                 $res = $this->db->query($sql);
  1609.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1610.                 
  1611.                 $sql = "UPDATE $tb SET $freh=$t_order WHERE $fid=$s_id";
  1612.                 $res = $this->db->query($sql);
  1613.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1614.             }
  1615.             elseif ($pos == NESE_MOVE_AFTER) {
  1616.                 $sql = "UPDATE $tb SET $freh=$freh+1
  1617.                         WHERE $freh BETWEEN $t_order AND $s_order 
  1618.                             AND 
  1619.                             $fid!=$t_id
  1620.                             AND 
  1621.                             $fid!=$s_id
  1622.                             AND 
  1623.                             $froot=" . $this->db->quote($rootid) . "
  1624.                             AND 
  1625.                             $flevel=" . $this->db->quote($level) . "
  1626.                             AND 
  1627.                             $flft BETWEEN $plft AND $prgt";
  1628.                 $res = $this->db->query($sql);
  1629.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1630.                 
  1631.                 $sql = "UPDATE $tb SET $freh=$t_order+1 WHERE $fid=$s_id";
  1632.                 $res = $this->db->query($sql);
  1633.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1634.             }
  1635.         }
  1636.         
  1637.         return $source->id;
  1638.     }
  1639.     
  1640.     // }}}
  1641.     // {{{ moveRoot2Root()
  1642.     
  1643.     /**
  1644.     * Moves rootnodes
  1645.     *
  1646.     * <pre>
  1647.     * +-- root1
  1648.     * |
  1649.     * +-\ root2
  1650.     * | |
  1651.     * | |-- subnode1 [target]
  1652.     * | |-- subnode2 [new]
  1653.     * | |-- subnode3
  1654.     * |
  1655.     * +-\ root3
  1656.     *  [|]  <-----------------------+
  1657.     *   |-- subnode 3.1 [target]    |
  1658.     *   |-\ subnode 3.2 [source] >--+
  1659.     *     |-- subnode 3.2.1
  1660.     * </pre>
  1661.     *
  1662.     * @param     object NodeCT $source    Source
  1663.     * @param     object NodeCT $target    Target
  1664.     * @param     object NodeCT $target    Parent
  1665.     * @param     string $pos              BEfore | AFter
  1666.     * @param     string $copy             Copy mode?
  1667.     * @access    private
  1668.     * @see        moveTree
  1669.     */
  1670.     function moveRoot2Root($source, $target, $pos, $copy)
  1671.     {
  1672.         $this->_debugMessage('moveRoot2Root($source, $target, $pos, $copy)');
  1673.         if(PEAR::isError($lock=$this->_setLock())) {
  1674.             return $lock;
  1675.         }
  1676.         
  1677.         $tb = $this->node_table;
  1678.         $flft = $this->flparams['l'];
  1679.         $frgt = $this->flparams['r'];
  1680.         $fid = $this->flparams['id'];
  1681.         $froot = $this->flparams['rootid'];
  1682.         $freh = $this->flparams['norder'];
  1683.         $s_order = $source->norder;
  1684.         $t_order = $target->norder;
  1685.         $s_id = $source->id;
  1686.         $t_id = $target->id;
  1687.         
  1688.         if ($s_order < $t_order) {
  1689.             if ($pos == NESE_MOVE_BEFORE) {
  1690.                 $sql = "UPDATE $tb SET $freh=$freh-1
  1691.                         WHERE $freh BETWEEN $s_order AND $t_order AND 
  1692.                             $fid!=$t_id AND 
  1693.                             $fid!=$s_id AND 
  1694.                             $froot=$fid";
  1695.                 $res = $this->db->query($sql);
  1696.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1697.                 $sql = "UPDATE $tb SET $freh=$t_order -1 WHERE $fid=$s_id";
  1698.                 $res = $this->db->query($sql);
  1699.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1700.             }
  1701.             elseif($pos == NESE_MOVE_AFTER) {
  1702.                 
  1703.                 $sql = "UPDATE $tb SET $freh=$freh-1
  1704.                         WHERE $freh BETWEEN $s_order AND $t_order AND 
  1705.                             $fid!=$s_id AND 
  1706.                             $froot=$fid";
  1707.                 $res = $this->db->query($sql);
  1708.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1709.                 
  1710.                 $sql = "UPDATE $tb SET $freh=$t_order WHERE $fid=$s_id";
  1711.                 $res = $this->db->query($sql);
  1712.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1713.             }
  1714.         }
  1715.         
  1716.         if ($s_order > $t_order) {
  1717.             if ($pos == NESE_MOVE_BEFORE) {
  1718.                 $sql = "UPDATE $tb SET $freh=$freh+1
  1719.                         WHERE $freh BETWEEN $t_order AND $s_order AND 
  1720.                             $fid != $s_id AND 
  1721.                             $froot=$fid";
  1722.                 $res = $this->db->query($sql);
  1723.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1724.                 
  1725.                 $sql = "UPDATE $tb SET $freh=$t_order WHERE $fid=$s_id";
  1726.                 $res = $this->db->query($sql);
  1727.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1728.             }
  1729.             elseif ($pos == NESE_MOVE_AFTER) {
  1730.                 $sql = "UPDATE $tb SET $freh=$freh+1
  1731.                         WHERE $freh BETWEEN $t_order AND $s_order AND 
  1732.                         $fid!=$t_id AND 
  1733.                         $fid!=$s_id AND 
  1734.                         $froot=$fid";
  1735.                 $res = $this->db->query($sql);
  1736.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1737.                 
  1738.                 $sql = "UPDATE $tb SET $freh=$t_order+1 WHERE $fid = $s_id";
  1739.                 $res = $this->db->query($sql);
  1740.                 $this->_testFatalAbort($res, __FILE__, __LINE__);
  1741.             }
  1742.         }
  1743.         
  1744.         return $source->id;
  1745.     }
  1746.     
  1747.     // }}}
  1748.     // +-----------------------+
  1749.     // | Helper methods        |
  1750.     // +-----------------------+
  1751.     // {{{ _testFatalAbort()
  1752.     
  1753.     /**
  1754.     * Error Handler
  1755.     *
  1756.     * Tests if a given ressource is a PEAR error object
  1757.     * ans raises a fatal error in case of an error object
  1758.     *
  1759.     * @param        object  PEAR::Error $errobj     The object to test
  1760.     * @param        string  $file   The filename wher the error occured
  1761.     * @param        int     $line   The line number of the error
  1762.     * @return   void
  1763.     * @access private
  1764.     */
  1765.     function _testFatalAbort($errobj, $file, $line)
  1766.     {
  1767.         if (!PEAR::isError($errobj)) {
  1768.             return false;
  1769.         }
  1770.         
  1771.         $this->_debugMessage('_testFatalAbort($errobj, $file, $line)');
  1772.         if ($this->debug) {
  1773.             $message = $errobj->getUserInfo();
  1774.             $code = $errobj->getCode();
  1775.             $msg = "$message ($code) in file $file at line $line";
  1776.         } else {
  1777.             $msg = $errobj->getMessage();
  1778.             $code = $errobj->getCode();        }
  1779.             
  1780.             $this->raiseError($msg, $code, PEAR_ERROR_TRIGGER, E_USER_ERROR);
  1781.     }
  1782.     
  1783.     // }}}
  1784.     // {{{ addListener()
  1785.     
  1786.     /**
  1787.     * Add an event listener
  1788.     *
  1789.     * Adds an event listener and returns an ID for it
  1790.     *
  1791.     * @param        string $event           The ivent name
  1792.     * @param        string  $listener       The listener object
  1793.     * @return   string
  1794.     * @access public
  1795.     */
  1796.     function addListener($event, &$listener)
  1797.     {
  1798.         $listenerID = uniqid('el');
  1799.         $this->eventListeners[$event][$listenerID] =& $listener;
  1800.         return $listenerID;
  1801.     }
  1802.     
  1803.     // }}}
  1804.     // {{{ removeListener()
  1805.     
  1806.     /**
  1807.     * Removes an event listener
  1808.     *
  1809.     * Removes the event listener with the given ID
  1810.     *
  1811.     * @param        string $event           The ivent name
  1812.     * @param        string  $listenerID     The listener's ID
  1813.     * @return   bool
  1814.     * @access public
  1815.     */
  1816.     function removeListener($event, $listenerID)
  1817.     {
  1818.         unset($this->eventListeners[$event][$listenerID]);
  1819.         return true;
  1820.     }
  1821.     
  1822.     // }}}
  1823.     // {{{ triggerEvent()
  1824.     
  1825.     /**
  1826.     * Triggers and event an calls the event listeners
  1827.     *
  1828.     * @param        string $event   The Event that occured
  1829.     * @param        object node $node A Reference to the node object which was subject to changes
  1830.     * @param        array $eparams  A associative array of params which may be needed by the handler
  1831.     * @return   bool
  1832.     * @access public
  1833.     */
  1834.     function triggerEvent($event, &$node, $eparams = false)
  1835.     {
  1836.         if ($this->skipCallbacks ||
  1837.             !isset($this->eventListeners[$event]) ||
  1838.             !is_array($this->eventListeners[$event]) ||
  1839.             count($this->eventListeners[$event]) == 0) {
  1840.             return false;
  1841.         }
  1842.         
  1843.         foreach($this->eventListeners[$event] as $key => $val) {
  1844.             if (!method_exists($val, 'callEvent')) {
  1845.                 return new PEAR_Error($this->_getMessage(NESE_ERROR_NOHANDLER), NESE_ERROR_NOHANDLER);
  1846.             }
  1847.             
  1848.             $val->callEvent($event, $node, $eparams);
  1849.         }
  1850.         
  1851.         return true;
  1852.     }
  1853.     
  1854.     // }}}
  1855.     // {{{ setAttr()
  1856.     
  1857.     /**
  1858.     * Sets an object attribute
  1859.     *
  1860.     * @param        array $attr     An associative array with attributes
  1861.     *
  1862.     * @return   bool
  1863.     * @access public
  1864.     */
  1865.     function setAttr($attr)
  1866.     {
  1867.         static $hasSetSequence;
  1868.         if (!isset($hasSetSequence)) {
  1869.             $hasSetSequence = false;
  1870.         }
  1871.         
  1872.         if (!is_array($attr) || count($attr) == 0) {
  1873.             return false;
  1874.         }
  1875.         
  1876.         foreach ($attr as $key => $val) {
  1877.             $this->$key = $val;
  1878.             if ($key == 'sequence_table') {
  1879.                 $hasSetSequence = true;
  1880.             }
  1881.             
  1882.             // only update sequence to reflect new table if they haven't set it manually
  1883.             if (!$hasSetSequence && $key == 'node_table') {
  1884.                 $this->sequence_table = $this->node_table . '_' . $this->flparams['id'];
  1885.             }
  1886.             if($key == 'cache' && is_object($val)) {
  1887.                 $this->_caching = true;
  1888.                 $GLOBALS['DB_NestedSet'] = & $this;
  1889.             }
  1890.         }
  1891.         
  1892.         return true;
  1893.     }
  1894.     
  1895.     // }}}
  1896.     // {{{ setDbOption()
  1897.     
  1898.     /**
  1899.     * Sets a db option.  Example, setting the sequence table format
  1900.     *
  1901.     * @var string $option The option to set
  1902.     * @var string $val The value of the option
  1903.     *
  1904.     * @access public
  1905.     * @return void
  1906.     */
  1907.     function setDbOption($option, $val)
  1908.     {
  1909.         $this->db->setOption($option, $val);
  1910.     }
  1911.     
  1912.     // }}}
  1913.     // {{{ testLock()
  1914.     
  1915.     /**
  1916.     * Tests if a database lock is set
  1917.     *
  1918.     * @access public
  1919.     */
  1920.     function testLock()
  1921.     {
  1922.         $this->_debugMessage('testLock()');
  1923.         if($lockID = $this->structureTableLock) {
  1924.             return $lockID;
  1925.         }
  1926.  
  1927.         $this->_lockGC();
  1928.         $tb = $this->lock_table;
  1929.         $stb = $this->node_table;
  1930.         $lockTTL = time() - $this->lockTTL;
  1931.         $sql = "SELECT lockID FROM $tb WHERE lockTable=" . $this->db->quote($stb);
  1932.         $res = $this->db->query($sql);
  1933.         $this->_testFatalAbort($res, __FILE__, __LINE__);
  1934.         
  1935.         if ($res->numRows()) {
  1936.             return new PEAR_Error($this->_getMessage(NESE_ERROR_TBLOCKED),NESE_ERROR_TBLOCKED);
  1937.         }
  1938.         
  1939.         return false;
  1940.     }
  1941.     
  1942.     // }}}
  1943.     // {{{ _setLock()
  1944.     
  1945.     /**
  1946.     * @access private
  1947.     */
  1948.     function _setLock()
  1949.     {
  1950.         $lock = $this->testLock();
  1951.         if(PEAR::isError($lock)) {
  1952.             return $lock;
  1953.         }
  1954.         
  1955.         $this->_debugMessage('_setLock()');
  1956.         if($this->_caching) {
  1957.             @$this->cache->flush('function_cache');
  1958.             $this->_caching = false;
  1959.             $this->_restcache = true;
  1960.         }
  1961.         $tb = $this->lock_table;
  1962.         $stb = $this->node_table;
  1963.         $stamp = time();
  1964.         if (!$lockID = $this->structureTableLock) {
  1965.             $lockID = $this->structureTableLock = uniqid('lck-');
  1966.             $sql = "INSERT INTO $tb SET
  1967.                         lockID=" . $this->db->quote($lockID) . ", 
  1968.                         lockTable=" . $this->db->quote($stb) . ", 
  1969.                         lockStamp=" . $this->db->quote($stamp);
  1970.         } else {
  1971.             $sql = "UPDATE $tb SET lockStamp=" . $this->db->quote($stamp) . "
  1972.                     WHERE lockID=" . $this->db->quote($lockID) . " AND
  1973.                         lockTable=" . $this->db->quote($stb);
  1974.         }
  1975.         
  1976.         $res = $this->db->query($sql);
  1977.         $this->_testFatalAbort($res, __FILE__, __LINE__);
  1978.         return $lockID;
  1979.     }
  1980.     
  1981.     // }}}
  1982.     // {{{ _releaseLock()
  1983.     
  1984.     /**
  1985.     * @access private
  1986.     */
  1987.     function _releaseLock()
  1988.     {
  1989.         $this->_debugMessage('_releaseLock()');
  1990.         if (!$lockID = $this->structureTableLock) {
  1991.             return false;
  1992.         }
  1993.         
  1994.         $tb = $this->lock_table;
  1995.         $stb = $this->node_table;
  1996.         $sql = "DELETE FROM $tb
  1997.                 WHERE lockTable=" . $this->db->quote($stb) . " AND 
  1998.                     lockID=" . $this->db->quote($lockID);
  1999.         $res = $this->db->query($sql);
  2000.         $this->_testFatalAbort($res, __FILE__, __LINE__);
  2001.         $this->structureTableLock = false;
  2002.         if($this->_restcache) {
  2003.             $this->_caching = true;
  2004.             $this->_restcache = false;
  2005.         }
  2006.         return true;
  2007.     }
  2008.     
  2009.     // }}}
  2010.     // {{{ _lockGC()
  2011.     
  2012.     /**
  2013.     * @access private
  2014.     */
  2015.     function _lockGC()
  2016.     {
  2017.         $this->_debugMessage('_lockGC()');
  2018.         $tb = $this->lock_table;
  2019.         $stb = $this->node_table;
  2020.         $lockTTL = time() - $this->lockTTL;
  2021.         $sql = "DELETE FROM $tb
  2022.                 WHERE lockTable=" . $this->db->quote($stb) . " AND 
  2023.                     lockStamp < $lockTTL";
  2024.         $res = $this->db->query($sql);
  2025.         $this->_testFatalAbort($res, __FILE__, __LINE__);
  2026.     }
  2027.     
  2028.     // }}}
  2029.     // {{{ _values2Query()
  2030.     
  2031.     /**
  2032.     * @access private
  2033.     */
  2034.     function _values2Query($values, $addval = false)
  2035.     {
  2036.         $this->_debugMessage('_values2Query($values, $addval = false)');
  2037.         if (is_array($addval)) {
  2038.             $values = $values + $addval;
  2039.         }
  2040.         
  2041.         $arq = array();
  2042.         foreach($values AS $key => $val) {
  2043.             $k = trim($key);
  2044.             $v = trim($val);
  2045.             if ($k) {
  2046.                 
  2047.                 $arq[] = "$k=" . $this->db->quote($v);
  2048.             }
  2049.         }
  2050.         
  2051.         if (!is_array($arq) || count($arq) == 0) {
  2052.             return false;
  2053.         }
  2054.         
  2055.         $query = implode(', ', $arq);
  2056.         return $query;
  2057.     }
  2058.     
  2059.     // }}}
  2060.     // {{{ _debugMessage()
  2061.     
  2062.     /**
  2063.     * @access private
  2064.     */
  2065.     function _debugMessage($msg)
  2066.     {
  2067.         if ($this->debug) {
  2068.             $time = $this->_getmicrotime();
  2069.             echo "$time::Debug:: $msg<br />\n";
  2070.         }
  2071.     }
  2072.     
  2073.     // }}}
  2074.     // {{{ _getMessage()
  2075.     
  2076.     /**
  2077.     * @access private
  2078.     */
  2079.     function _getMessage($code)
  2080.     {
  2081.         $this->_debugMessage('_getMessage($code)');
  2082.         return isset($this->messages[$code]) ? $this->messages[$code] : $this->messages[NESE_MESSAGE_UNKNOWN];
  2083.     }
  2084.     
  2085.     // }}}
  2086.     // {{{ _getmicrotime()
  2087.     
  2088.     /**
  2089.     * @access private
  2090.     */
  2091.     function _getmicrotime()
  2092.     {
  2093.         list($usec, $sec) = explode(' ', microtime());
  2094.         return ((float)$usec + (float)$sec);
  2095.     }
  2096.     
  2097.     // }}}
  2098.  
  2099. }
  2100. // {{{ DB_NestedSet_Node:: class
  2101.  
  2102. /**
  2103. * Generic class for node objects
  2104. *
  2105. * @autor Daniel Khan <dk@webcluster.at>;
  2106. * @version $Revision: 1.31 $
  2107. * @package DB_NestedSet
  2108. *
  2109. * @access private
  2110. */
  2111.  
  2112. // }}}
  2113. class DB_NestedSet_Node {
  2114.     // {{{ constructor
  2115.     
  2116.     /**
  2117.     * Constructor
  2118.     */
  2119.     function DB_NestedSet_Node($data)
  2120.     {
  2121.         if (!is_array($data) || count($data) == 0) {
  2122.             return new PEAR_ERROR($data, NESE_ERROR_PARAM_MISSING);
  2123.         }
  2124.         
  2125.         $this->setAttr($data);
  2126.         return true;
  2127.     }
  2128.     
  2129.     // }}}
  2130.     // {{{ setAttr()
  2131.     
  2132.     function setAttr($data)
  2133.     {
  2134.         if(!is_array($data) || count($data) == 0) {
  2135.             return false;
  2136.         }
  2137.         
  2138.         foreach ($data as $key => $val) {
  2139.             $this->$key = $val;
  2140.         }
  2141.     }
  2142.     
  2143.     // }}}
  2144.     
  2145. }
  2146. ?>
  2147.